EXECVE(2) | Linux Programmer's Manual | EXECVE(2) |
名前¶
execve - プログラムを実行する
書式¶
#include <unistd.h>
int execve(const char *filename, char *const
argv[],
char *const envp[]);
説明¶
execve() は、filename によって指定されたプログラムを実行する。 filename は、バイナリ実行形式か、 以下の形式の行で始まるスクリプトでなければならない。
#! interpreter [optional-arg]
後者の詳細は、後ろの「インタプリタ・スクリプト」の節を参照のこと。
argv は新しいプログラムに渡される引き数文字列の配列である。 envp は文字列の配列であり、伝統的に key=value の形式を しており、新しいプログラムの環境変数として渡される。 argv と envp はいずれもの NULL ポインタで終わっている 必要がある。引き数配列と環境変数は、呼び出されたプログラムの main 関数を 以下のように定義することによってアクセス可能になる。
int main(int argc, char *argv[], char *envp[])
成功した場合、 execve() は返らない。 そして、呼び出し元のプロセスの text, data, bss, スタックは、 読み込まれたプログラムによって上書きされる。
元のプログラムが ptrace されている場合、 execve() が成功した後に そのプログラムに SIGTRAP が送られる。
filename で指定されたプログラムファイルに set-user-ID ビットが設定されており、 ファイルが存在するファイルシステムが nosuid (mount(2) の MS_NOSUID フラグ) でマウントされておらず、 呼び出したプロセスが ptrace されていない場合、 呼び出したプロセスの実効 (effective) ユーザ ID は プログラムファイルの所有者 (owner) に変更される。 同様に、プログラムファイルに set-group-ID ビットが設定されていた場合、 呼び出したプロセスの有効グループ ID は プログラムファイルのグループに変更される。
プロセスの実効ユーザ ID は保存 (saved) set-user-ID にコピーされる。 同様に、実効グループ ID は保存 set-group-ID にコピーされる。 このコピーは、set-user-ID / set-group-ID 許可ビットにより発生する 実効 ID の変更後に行われる。
実行ファイルが動的リンクされた a.out 実行形式で、共有ライブラリの スタブを含むものだった場合、実行の開始時に Linux の ダイナミック・リンカ ld.so(8) が呼び出され、必要な共有ライブラリをメモリに読み込んでリンクを行う。
実行ファイルがダイナミック・リンクされた ELF 実行形式だった場合、 PT_INTERP セグメントに指定されたインタプリタが必要な 共有ライブラリ (shared library) を読み込むのに使用される。 通常、インタプリタとしては、 Linux libc 5 をリンクしたバイナリの場合には /lib/ld-linux.so.1 が、 glibc 2 をリンクしたバイナリの場合には /lib/ld-linux.so.2 が使用される。
以下に示す以外のすべてのプロセス属性は execve() の前後で保持される。
- 処理待ち (pending) のシグナル集合はクリアされる (sigpending(2))。
- 捕捉されたシグナルの処理方法 (disposition) は「無視」にリセットされる。
- 代替シグナルスタックはどれも保持されない (sigaltstack(2))。
- メモリマッピングは保持されない (mmap(2))。
- 付加された (attached) System V 共有メモリセグメントは分離される (shmat(2))。
- POSIX 共有メモリ領域はマッピングを解除される (shm_open(3))。
- オープンされた POSIX メッセージキューディスクリプタはクローズされる (mq_overview(7))。
- オープンされた POSIX 名前付きセマフォはいずれもクローズされる (sem_overview(7))。
- POSIX タイマは保持されない (timer_create(2))。
- オープンされたディレクトリストリームはいずれもクローズされる (opendir(3))。
- メモリロックは保持されない (mlock(2), mlockall(2))。
- 終了 (exit) ハンドラは保持されない (atexit(3), on_exit(3))。
- 浮動小数点関連の環境はデフォルトにリセットされる (fenv(3) 参照)。
上記のリストのプロセス属性はいずれも POSIX.1-2001 で規定されている。 以下に示す Linux 固有のプロセス属性も execve() の前後で保持されない。
- set-user-ID か set-group-ID されたプログラムが実行されている場合、 prctl(2) の PR_SET_DUMPABLE フラグはクリアされる。それ以外の場合、このフラグはセットされる。
- prctl(2) の PR_SET_KEEPCAPS フラグはクリアされる。
- プロセス名は新しい実行ファイルの名前にリセットされる。 プロセス名は prctl(2) の PR_SET_NAME で設定でき、 ps -o comm で表示できる。
- 終了シグナル (termination signal) は SIGCHLD にリセットされる (clone(2) 参照)。
以下の点についても注意すること:
- 呼び出し元スレッド以外の全てのスレッドは execve() 中に破棄される。 mutex、条件変数、その他の pthread オブジェクトは保持されない。
- setlocale(LC_ALL, "C") 相当の処理がプログラム開始時に実行される。
- POSIX.1-2001 は、動作が無視かデフォルトに設定されている全てのシグナル の処理方法は変更せずそのままにする、と規定している。 但し、POSIX.1-2001 には一つ例外があり、 SIGCHLD が無視になっている場合、 その処理方法を変更せずにそのままにするか、デフォルト動作にリセットするかは 実装依存となっている。 Linux では前者 (変更しない) となっている。
- 完了していない非同期 I/O 操作はキャンセルされる (aio_read(3), aio_write(3))。
- execve(2) 時のケーパビリティの扱いについては、 capabilities(7) を参照。
- デフォルトでは、ファイルディスクリプタは execve() を行った後でもオープンされたままである。 close-on-exec の印が付いているファイルディスクリプタはクローズされる。 fcntl(2) の FD_CLOEXEC の説明を参照。 (ファイルディスクリプタがクローズされると、このプロセスが ファイルディスクリプタに対応するファイルに対して獲得していた レコードのロックが全て解放されることになる。) POSIX.1-2001 では、 ファイルディスクリプタ 0, 1, 2 が execve() 成功後にどこかでクローズされ、かつ 実行されるファイルに set-user_ID か set-group_ID の許可ビットが セットされていてプロセスが特権を獲得した場合、 システムは何らかのファイルをオープンする際に これらの番号のディスクリプタのどれかを使うことがある、 とされている。 原則として、移植性が必要なプログラムでは、 特権の有無に関わらず、 execve() の前後でこれら 3つのファイルディスクリプタがクローズされたままで あることを前提にすることはできない。
インタプリタ・スクリプト¶
インタプリタ・スクリプトとは、実行許可が有効になっていて、 最初の行が以下の形になっているテキストファイルのことである。
#! interpreter [optional-arg]
interpreter は有効な実行ファイルのパス名でなければならず、 それ自身がスクリプトであってはならない。 execve() の filename 引き数がインタプリタスクリプトを指定している場合、 interpreter は以下の引き数で起動される。
interpreter [optional-arg] filename arg...
arg... は execve() の argv 引き数が指すワード列である。
移植性を持たすには、 optional-arg は空か 1ワードだけにすべきである (つまり、ホワイト・スペースを含めるべきではない)。 下記の「注意」の節を参照。
引き数と環境変数の合計サイズの上限¶
ほとんどの Unix の実装は、新しいプログラムに渡すことができる コマンドライン引き数 (argv) と環境変数 (envp) の文字列群の合計サイズに何らかの上限を設けている。 POSIX.1 は、 ARG_MAX 定数を使ってこの上限を決める実装を認めている (ARG_MAX は <limits.h> で定義されるか、実行時に sysconf(_SC_ARG_MAX) の呼び出しで入手できるかのいずれかである)。
カーネル 2.6.23 より前の Linux では、環境変数と引き数の文字列群を 格納するのに使用されるメモリは 32 ページに制限されていた (32 ページというのはカーネル定数 MAX_ARG_PAGES で定義される)。したがって、 ページサイズが 4 kB のアーキテクチャでは、 最大サイズは 128 kB ということになる。
カーネル 2.6.23 以降では、ほとんどのアーキテクチャにおいて、 execve() が呼び出された時点で適用されているリソースのソフト上限 RLIMIT_STACK に基づいたサイズ上限が使われる (メモリ管理ユニット (MMU) を持たないアーキテクチャは上記の変更の 例外であり、これらのアーキテクチャではカーネル 2.6.23 より前と 同じ上限がそのまま使用される)。 これらのアーキテクチャでは、合計サイズは許可されたスタックサイズの 1/4 に制限されている (1/4 の上限を設けているのは、新しいプログラムが必ずある程度の スタック空間を持てることを保証するためである)。 Linux 2.6.25 以降では、カーネルはこのサイズ上限に 32 ページの下限を 設けている。これにより、 RLIMIT_STACK が非常に小さく設定された場合でも、アプリケーションが少なくとも Linux 2.6.23 以前で提供されていたのと同じ大きさの引き数と環境変数の空間 と同じだけは確保できることが保証されている (この最低限の保証は Linux 2.6.23 と 2.6.24 では提供されていない)。 また、各文字列の上限は 32 ページ (カーネル定数 MAX_ARG_STRLEN) で、文字列数の最大値は 0x7FFFFFFF である。
返り値¶
成功すると execve() は返らない。エラーの場合は -1 を返し、 errno を適切に設定する。
エラー¶
- E2BIG
- 環境変数 (envp) と引き数リスト (argv) の合計バイト数が大き過ぎる。
- EACCES
- filename やスクリプトインタプリタ名の構成要素に検索許可 (search permission) が与えられていない (path_resolution(7) も参照すること)。
- EACCES
- ファイルもしくはスクリプトのインタプリタが通常ファイル (regular file) でない。
- EACCES
- ファイルやスクリプトや ELF インタプリタに 実行許可 (execute permission) が与えられていない。
- EACCES
- ファイル・システムが noexec でマウントされている。
- EFAULT
- filename がアクセス可能なアドレス空間の外を指している。
- EINVAL
- ELF 実行形式で複数の PT_INTERP セグメントが存在する。 (すなわち複数のインタプリタを指定した。)
- EIO
- I/O エラーが発生した。
- EISDIR
- ELF インタプリタがディレクトリだった。
- ELIBBAD
- ELF インタプリタが理解できるフォーマットでなかった。
- ELOOP
- filename やスクリプトや ELF のインタプリタを解決する際に遭遇した シンボリック・リンクが多過ぎる。
- EMFILE
- そのプロセスがオープンできるファイル数の上限まで既にオープンしている。
- ENAMETOOLONG
- filename が長過ぎる。
- ENFILE
- そのシステムでオープンできるファイル数の制限に達した。
- ENOENT
- ファイル filename かスクリプトや ELF のインタプリタが存在しない。
- ENOEXEC
- 実行ファイルが理解できない形式であるか、違うアーキテクチャのものか、 その他のフォーマット・エラーにより実行ができなかった。
- ENOMEM
- カーネルに十分なメモリがない。
- ENOTDIR
- filename やスクリプトや ELF のインタプリタの構成要素がディレクトリでない。
- EPERM
- ファイル・システムが nosuid でマウントされ、ユーザがスーパーユーザでなく、 ファイルに set-user-ID あるいは set-group-ID ビットが設定されている。
- EPERM
- プロセスがトレースされ、ユーザがスーパーユーザでなく、 ファイルに set-user-ID あるいは set-group-ID ビットが設定されている。
- ETXTBSY
- 実行ファイルを書き込み用にオープンしているプロセスがある。
準拠¶
SVr4, 4.3BSD, POSIX.1-2001. POSIX.1-2001 には #! 動作についての記述はないが、 他は互換性がある。
注意¶
set-user-id プロセスと set-group-ID プロセスは ptrace(2) できない。
Linux はスクリプトの set-user-ID と set-group-ID ビットを無視する。
ファイルシステムを nosuid でマウントした場合に set-user-ID/set-group-ID の実行ファイルを どの様に扱うかは、Linux カーネルのバージョンによって異なる: あるバージョンでは、すでに必要な権限を持っている場合を除いて、 その実行を拒否する (そして EPERM を返す)。別のあるバージョンでは set-user-ID/set-group-ID ビットのみを無視し exec() は成功する。
#! 実行形式のシェル・スクリプトの 1行目に許されている文字数は、 最大 127 文字である。
インタプリタ・スクリプトの optional-arg 引き数の解釈方法は実装により異なる。 Linux では、インタプリタ名 interpreter に続く文字列全体がインタプリタに 1個の引き数として渡される。 しかし、動作が異なるシステムもある。 あるシステムでは、 optional-arg のうち最初のホワイト・スペースまでが 引き数として渡される。 また、別のシステムでは インタプリタ・スクリプトは複数の引き数を持つことができ、 optional-arg 内のホワイト・スペースが引き数の区切りとなる。
Linux では、 argv に NULL を指定することができる。これは、この引き数に NULL ポインタ 1個だけを含むリストへのポインタを指定したのと同じ効果を持つ。 「この間違った機能を利用しないこと」。 これは非標準で、移植性もない。 他のほとんどの Unix システムでは、これを行うとエラー (EFAULT) になる。
POSIX.1-2001 は、 sysconf(3) が返す値はプロセスの生存中は変化しないべきだとしている。 しかしながら、Linux 2.6.23 以降では、リソース上限 RLIMIT_STACK が変化した場合、 コマンドライン引き数と環境変数を保持するための空間に対する上限が 変化したことを反映して、 _SC_ARG_MAX が返す値も変化する。
歴史¶
Unix V6 では exec() コールの引き数リストは 0 で終端され、 main の引き数リストは -1 で終端されていた。 そのため、 main の引き数リストは、その後の exec() コールには直接使用できなかった。 Unix V7 以降では、ともに NULL で終端される。
例¶
このプログラムは、以下の二つ目のプログラムから実行するためのものである。 コマンドラインを 1行に 1個ずつ表示するだけのプログラムである。
/* myecho.c */ #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) {
int j;
for (j = 0; j < argc; j++)
printf("argv[%d]: %s\n", j, argv[j]);
exit(EXIT_SUCCESS); }
以下のプログラムは、コマンドライン引き数で指定した名前のプログラムを
実行するのに使う。
/* execve.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> int main(int argc, char *argv[]) {
char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
assert(argc == 2); /* argv[1] identifies
program to exec */
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() only returns on error */
exit(EXIT_FAILURE); }
二つ目のプログラムを使って一つ目のプログラムを実行するには 以下のようにする。
$ cc myecho.c -o myecho $ cc execve.c -o execve $ ./execve ./myecho argv[0]: ./myecho argv[1]: hello argv[2]: world
さらに、これらのプログラムを使って、スクリプト・インタプリタの例を示す。 このために、「インタプリタ」として先ほど作成したプログラム myecho を使うスクリプトを作成する。
$ cat > script.sh #! ./myecho script-arg ^D $ chmod +x script.sh
作成しておいたプログラムを使ってスクリプトを実行する。
$ ./execve ./script.sh argv[0]: ./myecho argv[1]: script-arg argv[2]: ./script.sh argv[3]: hello argv[4]: world
関連項目¶
chmod(2), fork(2), ptrace(2), execl(3), fexecve(3), getopt(3), credentials(7), environ(7), path_resolution(7), ld.so(8)
2008-11-28 | Linux |